package kubernetescli

import (
	"context"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/Kindling-project/chaos-backend/api"
	"github.com/Kindling-project/chaos-backend/tools/wrapper"
	"github.com/chaos-mesh/chaos-mesh/api/v1alpha1"
)

var DefaultServiceMethodEntryMap *ServiceMethodEntryMap

func init() {
	DefaultServiceMethodEntryMap = NewServiceMethodEntryMap()
	DefaultServiceMethodEntryMap.AddMethodEntryForJVMChaos(
		"ts-basic-service",
		&MethodEntry{
			Class:  "fdse.microservice.service.BasicServiceImpl",
			Method: "queryForTravels",
		},
	)
	DefaultServiceMethodEntryMap.AddMethodEntryForJVMChaos(
		"ts-travel-service",
		&MethodEntry{
			Class:  "travel.service.TravelServiceImpl",
			Method: "queryByBatch",
		},
	)
	DefaultServiceMethodEntryMap.AddMethodEntryForJVMChaos(
		"ts-station-service",
		&MethodEntry{
			Class:  "fdse.microservice.service.StationServiceImpl",
			Method: "queryForIdBatch",
		},
	)
	DefaultServiceMethodEntryMap.AddMethodEntryForJVMChaos(
		"ts-train-service",
		&MethodEntry{
			Class:  "train.service.TrainServiceImpl",
			Method: "retrieveByNames",
		},
	)
	DefaultServiceMethodEntryMap.AddMethodEntryForJVMChaos(
		"ts-route-service",
		&MethodEntry{
			Class:  "route.service.RouteServiceImpl",
			Method: "getRouteByIds",
		},
	)
	DefaultServiceMethodEntryMap.AddMethodEntryForJVMChaos(
		"ts-price-service",
		&MethodEntry{
			Class:  "price.service.PriceServiceImpl",
			Method: "findByRouteIdsAndTrainTypes",
		},
	)
	DefaultServiceMethodEntryMap.AddMethodEntryForJVMChaos(
		"ts-seat-service",
		&MethodEntry{
			Class:  "seat.service.SeatServiceImpl",
			Method: "getLeftTicketOfInterval",
		},
	)
	DefaultServiceMethodEntryMap.AddMethodEntryForJVMChaos(
		"ts-order-service",
		&MethodEntry{
			Class:  "order.service",
			Method: "OrderServiceImpl.getSoldTickets",
		},
	)
	DefaultServiceMethodEntryMap.AddMethodEntryForJVMChaos(
		"ts-config-service",
		&MethodEntry{
			Class:  "config.service.ConfigServiceImpl",
			Method: "query",
		},
	)
	DefaultServiceMethodEntryMap.AddMethodEntryForJVMChaos(
		"ts-travel-plan-service",
		&MethodEntry{
			Class:  "travelplan.service.TravelPlanServiceImpl",
			Method: "getCheapest",
		},
	)
	DefaultServiceMethodEntryMap.AddMethodEntryForJVMChaos(
		"ts-route-plan-service",
		&MethodEntry{
			Class:  "plan.service.RoutePlanServiceImpl",
			Method: "searchCheapestResult",
		},
	)
	DefaultServiceMethodEntryMap.AddMethodEntryForJVMChaos(
		"ts-travel2-service",
		&MethodEntry{
			Class:  "travel2.service.TravelServiceImpl",
			Method: "queryByBatch",
		},
	)
	DefaultServiceMethodEntryMap.AddMethodEntryForJVMChaos(
		"ts-order-other-service",
		&MethodEntry{
			Class:  "other.service.OrderOtherServiceImpl",
			Method: "getSoldTickets",
		},
	)
}

type ServiceMethodEntryMap struct {
	sync.Map
}

func NewServiceMethodEntryMap() *ServiceMethodEntryMap {
	return &ServiceMethodEntryMap{}
}

func (smm *ServiceMethodEntryMap) AddMethodEntryForJVMChaos(service string, entry *MethodEntry) {
	smm.Store(service, entry)
}

func (smm *ServiceMethodEntryMap) CheckService(service string) (*MethodEntry, bool) {
	if entry, ok := smm.Load(service); ok {
		return entry.(*MethodEntry), true
	}

	return nil, false
}

type MethodEntry struct {
	Class  string
	Method string
}

type jvmchaos struct {
}

// 资源转换
func (n *jvmchaos) ToChaosVO(obj *interface{}) []api.Chaos { //资源转化函数
	return wrapper.JVMChaosToChaosVO(obj)
}

// 查看chaosmesh中有多少个jvm相关的故障（已完成）
func (n *jvmchaos) List() (interface{}, error) {
	if client == nil {
		return nil, errors.New("kube-client not initialized yet")
	}

	result := v1alpha1.JVMChaosList{}
	err := client.
		Get().
		Resource("jvmchaos").
		Namespace("train-ticket").
		Do(context.Background()).
		Into(&result)

	return result, err
}

// 在这里将jvm的gc和cpu的故障写死。。
var defaultJVMChaosParam = v1alpha1.JVMChaos{
	Spec: v1alpha1.JVMChaosSpec{
		Action: v1alpha1.JVMRuleDataAction,
		JVMParameter: v1alpha1.JVMParameter{
			JVMCommonSpec: v1alpha1.JVMCommonSpec{
				Port: 9277,
			},
		},
	},
}

var cpuCost = `RULE jvm-cpu-load
CLASS %s
METHOD %s
HELPER com.kindling.helper.CpuHelper
AT ENTRY
IF true
DO System.out.println("start calculating");
	calculate()
ENDRULE
`

func addCpuCostRuleData(chaos *v1alpha1.JVMChaos) error {
	target := chaos.Annotations["target"]
	if entryData, ok := DefaultServiceMethodEntryMap.Load(target); ok {
		entry := entryData.(*MethodEntry)
		chaos.Spec.RuleData = fmt.Sprintf(cpuCost, entry.Class, entry.Method)
		return nil
	}

	return fmt.Errorf("target haven't defined target method")
}

var gc = `RULE gc-trigger
CLASS %s
METHOD %s
HELPER com.kindling.helper.GcHelper
AT ENTRY
IF true
DO System.out.println("create big objects");
	gcTriggerFull()
ENDRULE
`

func addGcRuleData(chaos *v1alpha1.JVMChaos) error {
	target := chaos.Annotations["target"]
	if entryData, ok := DefaultServiceMethodEntryMap.Load(target); ok {
		entry := entryData.(*MethodEntry)
		chaos.Spec.RuleData = fmt.Sprintf(gc, entry.Class, entry.Method)
		return nil
	}

	return fmt.Errorf("target haven't defined target method")
}

var exception_rule = `RULE throw-jvm-exception
CLASS %s
METHOD %s
AT ENTRY
IF true
DO
	throw new java.lang.RuntimeException("throwed by Chaos-Mesh");
ENDRULE`

func addExceptionRuleData(chaos *v1alpha1.JVMChaos) error {
	target := chaos.Annotations["target"]
	if entryData, ok := DefaultServiceMethodEntryMap.Load(target); ok {
		entry := entryData.(*MethodEntry)
		chaos.Spec.RuleData = fmt.Sprintf(exception_rule, entry.Class, entry.Method)
		return nil
	}
	return fmt.Errorf("target haven't defined target method")
}

// 创建jvmchaos
func (n *jvmchaos) Create(vo *api.Chaos) (interface{}, error) {
	if client == nil {
		return nil, errors.New("kube-client not initialized yet")
	}

	n.DeleteAll()

	var action, rule string
	if vo.ComplexAction != "" {
		typeAndAction := strings.Split(vo.ComplexAction, "#")
		if len(typeAndAction) > 2 {
			action = typeAndAction[1]
			rule = typeAndAction[2]
		}
	}

	var jvmchaos *v1alpha1.JVMChaos

	if action == "ruleData" {
		switch rule {
		case "cpucost":
			jvmchaos = buildJVMChaos(vo, defaultJVMChaosParam)
			if err := addCpuCostRuleData(jvmchaos); err != nil {
				return nil, err
			}
		case "gcrule":
			jvmchaos = buildJVMChaos(vo, defaultJVMChaosParam)
			// TODO
			if err := addGcRuleData(jvmchaos); err != nil {
				return nil, err
			}
		case "exception":
			jvmchaos = buildJVMChaos(vo, defaultJVMChaosParam)
			if err := addExceptionRuleData(jvmchaos); err != nil {
				return nil, err
			}
		}
	}

	// var ruleDataOption string

	result := v1alpha1.JVMChaos{}
	err := client.
		Post().
		Namespace(jvmchaos.Namespace).
		Resource("jvmchaos").
		Body(jvmchaos).
		Do(context.Background()).
		Into(&result)
	return result, err

}

// 删除jvm的所有chaos
func (n *jvmchaos) DeleteAll() (interface{}, error) {
	if client == nil {
		return nil, errors.New("kube-client not initialized yet")
	}

	result := v1alpha1.JVMChaosList{}
	err := client.
		Get().
		Resource("jvmchaos").
		Do(context.Background()).
		Into(&result)

	if err != nil {
		return nil, err
	}

	var lastChaos v1alpha1.JVMChaos
	for _, chaos := range result.Items {
		if v, ok := chaos.Labels["kindling-chaos"]; ok && v == "train-ticket" {
			result := v1alpha1.JVMChaos{}
			err := client.
				Delete().
				Resource("jvmchaos").
				Name(chaos.Name).
				Namespace(chaos.Namespace).
				Do(context.Background()).
				Into(&result)

			if err != nil {
				return nil, err
			}
			lastChaos = chaos
		}
	}

	return lastChaos, err

}

// 构造jvmchaos
func buildJVMChaos(vo *api.Chaos, params v1alpha1.JVMChaos) *v1alpha1.JVMChaos {
	var resource = params.DeepCopy()
	ts := time.Now().Unix()

	resource.Name = "jvmchaos" + "-" + strconv.FormatInt(ts, 10)
	resource.Namespace = "train-ticket"
	// Mark labels
	resource.Labels = map[string]string{
		"kindling-chaos": "train-ticket",
	}
	resource.Annotations = map[string]string{
		"describe":       vo.Desc,
		"complex-action": vo.ComplexAction,
	}

	resource.Spec.Action = v1alpha1.JVMChaosAction(vo.ChaosAction)
	// result.Spec.Action = v1alpha1.JVMRuleDataAction("s")
	for i := 0; i < len(vo.Params); i++ {
		switch vo.Params[i].Name {
		case "instance":
			resource.Spec.Selector.LabelSelectors = map[string]string{
				"app": vo.Params[i].Value,
			}
			resource.Annotations["target"] = vo.Params[i].Value
			resource.Spec.Mode = v1alpha1.AllMode
		}
	}

	return resource
}
